package cn.bytepulse.runner;

import cn.bytepulse.annotation.Anonymous;
import cn.bytepulse.annotation.Commercial;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 匿名和商用接口扫描
 *
 * @author jiejiebiezheyang
 * @since 2024-06-26 00:00
 */
@Slf4j
@Component
public class AnonymousAndCommercialScan implements BeanFactoryPostProcessor {

    public final static List<String> ANONYMOUS = new ArrayList<>();
    public final static List<String> COMMERCIAL = new ArrayList<>();

    static {
        ANONYMOUS.add("/favicon.ico");
        ANONYMOUS.add("/swagger-ui.html");
        ANONYMOUS.add("/swagger-ui/*");
        ANONYMOUS.add("/swagger-resources/**");
        ANONYMOUS.add("/v2/api-docs");
        ANONYMOUS.add("/v3/api-docs");
        ANONYMOUS.add("/v3/api-docs/**");
        ANONYMOUS.add("/doc.html");
        ANONYMOUS.add("/webjars/**");
        ANONYMOUS.add("/druid/**");
        ANONYMOUS.add("/actuator/**");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            log.info("自定义扫描开始");
            long startTime = System.currentTimeMillis();
            Set<String> clazzSet = scanPackages("**.controller");

            for (String className : clazzSet) {
                Class<?> clazz = Class.forName(className);

                if (!(clazz.isAnnotationPresent(RestController.class) || clazz.isAnnotationPresent(Controller.class))) {
                    continue;
                }

                // 接口前缀
                String controllerPrefix = extractPath(clazz.getAnnotation(RequestMapping.class));
                boolean classHasAnonymous = clazz.isAnnotationPresent(Anonymous.class);
                boolean classHasCommercial = clazz.isAnnotationPresent(Commercial.class);

                for (Method method : clazz.getDeclaredMethods()) {
                    // 有匿名注解
                    boolean hasAnonymous = classHasAnonymous || method.isAnnotationPresent(Anonymous.class);
                    // 有商用注解
                    boolean hasCommercial = classHasCommercial || method.isAnnotationPresent(Commercial.class);
                    // 都没有跳过
                    if (!(hasAnonymous || hasCommercial)) continue;

                    String methodPath = extractPathFromMethod(method);
                    String fullPath = (controllerPrefix + "/" + methodPath).replaceAll("//", "/").replaceAll(" ", "").replaceAll("\\{[^}]+}", "*");

                    if (fullPath.endsWith("/")) fullPath = fullPath.substring(0, fullPath.length() - 1);
                    if (hasAnonymous) {
                        // 添加匿名接口路径
                        ANONYMOUS.add(fullPath);
                    }
                    if (hasCommercial) {
                        // 添加商用接口路径
                        COMMERCIAL.add(fullPath);
                        // 商用接口也需要放心
                        ANONYMOUS.add(fullPath);
                    }
                }
            }
            long endTime = System.currentTimeMillis();
            log.info("扫描完成总耗时:{}", (endTime - startTime) + "毫秒");
            log.info("匿名接口:{}", ANONYMOUS);
            log.info("商用接口:{}", COMMERCIAL);
        } catch (Exception e) {
            throw new BeansException("自定义扫描失败喵：" + e.getMessage(), e) {
            };
        }
    }

    private String extractPath(Annotation mapping) {
        if (mapping == null) return "";
        try {
            Method path = mapping.annotationType().getMethod("path");
            String[] paths = (String[]) path.invoke(mapping);
            if (paths.length > 0) return paths[0];

            Method value = mapping.annotationType().getMethod("value");
            String[] values = (String[]) value.invoke(mapping);
            if (values.length > 0) return values[0];
        } catch (Exception ignored) {
        }
        return "";
    }

    private String extractPathFromMethod(Method method) {
        Class<? extends Annotation>[] mappings = new Class[]{
                RequestMapping.class,
                GetMapping.class,
                PostMapping.class,
                PutMapping.class,
                DeleteMapping.class,
                PatchMapping.class
        };

        for (Class<? extends Annotation> annClass : mappings) {
            if (method.isAnnotationPresent(annClass)) {
                return extractPath(method.getAnnotation(annClass));
            }
        }
        return "";
    }

    private static Set<String> scanPackages(String basePackage) throws IOException {
        Set<String> classNames = new HashSet<>();

        ClassPathScanningCandidateComponentProvider scanner =
                new ClassPathScanningCandidateComponentProvider(false);

        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(resolver);

        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                basePackage.replace('.', '/') + "/**/*.class";

        Resource[] resources = resolver.getResources(packageSearchPath);

        for (Resource resource : resources) {
            if (resource.isReadable()) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                String className = metadataReader.getClassMetadata().getClassName();
                classNames.add(className);
            }
        }

        return classNames;
    }
}
